package tech

import (
	"slices"
	"ssp/set"
)

// 新增：查找满足两行两列两宫的四格矩形
func (g *Grid) FindRects() [][]*Cell {
	var rects [][]*Cell

	for tier := range 3 {
		for rowNum0 := tier * 3; rowNum0 < tier*3+2; rowNum0++ {
			for colNum0 := range 6 {
				cell0 := g.rows[rowNum0].cells[colNum0]
				if cell0.given {
					continue
				}
				for colNum1 := colNum0/3*3 + 3; colNum1 < 9; colNum1++ {
					cell1 := g.rows[rowNum0].cells[colNum1]
					if cell1.given {
						continue
					}
					for rowNum1 := rowNum0 + 1; rowNum1 < tier*3+3; rowNum1++ {
						cell2 := g.rows[rowNum1].cells[colNum0]
						if cell2.given {
							continue
						}
						cell3 := g.rows[rowNum1].cells[colNum1]
						if cell3.given {
							continue
						}
						rect := []*Cell{cell0, cell1, cell2, cell3}
						rects = append(rects, rect)
					}
				}
			}
		}
	}

	for stack := range 3 {
		for rowNum0 := range 6 {
			for colNum0 := stack * 3; colNum0 < stack*3+2; colNum0++ {
				cell0 := g.rows[rowNum0].cells[colNum0]
				if cell0.given {
					continue
				}
				//for rowNum1 := rowNum0 + 1; rowNum1 < tier*3+3; rowNum1++ {
				for colNum1 := colNum0 + 1; colNum1 < stack*3+3; colNum1++ {
					cell1 := g.rows[rowNum0].cells[colNum1]
					if cell1.given {
						continue
					}
					//for colNum1 := colNum0/3*3 + 3; colNum1 < 9; colNum1++ {
					for rowNum1 := rowNum0/3*3 + 3; rowNum1 < 9; rowNum1++ {
						cell2 := g.rows[rowNum1].cells[colNum0]
						if cell2.given {
							continue
						}
						cell3 := g.rows[rowNum1].cells[colNum1]
						if cell3.given {
							continue
						}
						rect := []*Cell{cell0, cell1, cell2, cell3}
						rects = append(rects, rect)
					}
				}
			}
		}
	}
	return rects
}

func (g *Grid) CounterDR(f func([]*Cell) (bool, []*Result)) (used bool, results []*Result) {
	rects := g.FindRects()
	for _, rect := range rects {
		if ok, rs := f(rect); ok {
			used = true
			if OUTPUTRESULT {
				results = append(results, rs...)
			}
			if !EXHAUSTIVE {
				return
			}
		}
	}

	return
}

func (s *Solve) CounterDR1(g *Grid) (used bool, results []*Result) {
	used, results = g.CounterDR(g.cdr1)
	return
}

func solvedCount(cells []*Cell) int {
	count := 0
	for _, cell := range cells {
		if cell.solved() {
			count++
		}
	}
	return count
}

func (g *Grid) cdr1(rect []*Cell) (used bool, results []*Result) {
	for i, removed := range rect {
		if removed.solved() {
			continue
		}
		cands := set.NewSet()
		for j, cell := range rect {
			if i == j {
				continue
			}
			if cell.solved() {
				cands.Add(cell.value - 1)
			} else {
				cands = cands.Union(cell.cands)
			}
		}
		if cands.Count() != 2 {
			continue
		}
		removed := rect[i]
		removedCands := removed.cands.Intersect(cands)
		if removedCands.Count() == 0 {
			continue
		}
		used = true
		if OUTPUTRESULT {
			result := new(Result)
			result.addRemoved(removedCands, rect[i:i+1])
			g1 := result.getHelpCandsGroup()
			var g2 HelpValuesGroup
			for _, cell := range rect {
				if cell.solved() {
					g2 = result.getHelpValuesGroup()
					break
				}
			}
			for j, cell := range rect {
				if i == j {
					continue
				}
				if cell.solved() {
					g2.add([]*Cell{cell})
				} else {
					g1.add(removedCands, []*Cell{cell})
				}
			}
			//result.addHelpCells(rect)
			result.setStrategyName("Counter Death Rectangle 1")
			results = append(results, result)
		}
		if CHANGE {
			removeCands(removed.cands.Intersect(cands), rect[i:i+1])
		}
		break
	}
	return
}

func (s *Solve) CounterDR2(g *Grid) (used bool, results []*Result) {
	used, results = g.CounterDR(g.cdr2)
	return
}

func (g *Grid) cdr2(rect []*Cell) (used bool, results []*Result) {
	cands := set.NewSet()
	for _, cell := range rect {
		if cell.solved() {
			cands.Add(cell.value - 1)
		} else {
			cands = cands.Union(cell.cands)
		}
	}
	if cands.Count() != 3 {
		return
	}

	candCells := make(map[int][]*Cell)
	for _, cell := range rect {
		if cell.solved() {
			candCells[cell.value-1] = append(candCells[cell.value-1], cell)
		} else {
			for _, cand := range cell.cands.GetList() {
				candCells[cand] = append(candCells[cand], cell)
			}
		}
	}
	zs := make(map[int][]*Cell)
	for cand, cells := range candCells {
		if len(cells) == 1 || len(cells) == 4 {
			continue
		}
		//fmt.Println("cdr2", cand, cells)
		found := false
		for _, pair := range combine(cells, 2) {
			if isSameHouse(pair) {
				//locked pair
				if pair[0].cands.Count() == 2 && pair[0].cands == pair[1].cands {
					found = true
					break
				}
			} else {
				//the cands of the diagonal cells can not be equal
				if pair[0].cands.Intersect(pair[1].cands).Count() == 0 {
					found = true
					break
				}
			}
		}
		if found {
			continue
		}
		zs[cand] = cells
	}
	for cand, zCells := range zs {
		z := set.NewSet(cand)
		if ok, rmCells := g.canRemoveSeenCands(z, zCells); ok {
			used = true
			if OUTPUTRESULT {
				result := new(Result)
				result.addRemoved(z, rmCells)
				g2 := result.getHelpCandsGroup()
				for _, cell := range zCells {
					g2.add(cell.cands.Diff(z), []*Cell{cell})
				}
				var g3 HelpValuesGroup
				for _, cell := range rect {
					if cell.solved() {
						g3 = result.getHelpValuesGroup()
						break
					}
				}
				for _, cell := range rect {
					if slices.Contains(zCells, cell) {
						continue
					}
					if cell.solved() {
						g3.add([]*Cell{cell})
					} else {
						g2.add(cands.Diff(z), []*Cell{cell})
					}
				}
				g1 := result.getHelpCandsGroup()
				g1.add(z, zCells)
				result.setStrategyName("Counter Death Rectangle 2")
				results = append(results, result)
			}
			if CHANGE {
				removeCands(z, rmCells)
			}
		}

	}

	return
}

func (s *Solve) CounterDR3(g *Grid) (used bool, results []*Result) {
	used, results = g.CounterDR(g.cdr3)
	return
}

func (g *Grid) cdr3(rect []*Cell) (used bool, results []*Result) {
	lines := [][2]int{{0, 1}, {2, 3}, {0, 2}, {1, 3}}
	otherLines := [][2]int{{2, 3}, {0, 1}, {1, 3}, {0, 2}}
	for i, line := range lines {
		c0, c1 := rect[line[0]], rect[line[1]]
		baseCands := c0.getCandsOrValue().Union(c1.getCandsOrValue())
		if baseCands.Count() != 2 {
			continue
		}
		c2, c3 := rect[otherLines[i][0]], rect[otherLines[i][1]]
		if c2.solved() || c3.solved() {
			continue
		}
		c21cands := c2.cands.Intersect(c1.getCandsOrValue())
		if c21cands.Count() == 0 {
			continue
		}
		c30cands := c3.cands.Intersect(c0.getCandsOrValue())
		if c30cands.Count() == 0 {
			continue
		}
		if c21cands.Union(c30cands) != baseCands {
			continue
		}
		cands := c2.cands.Union(c3.cands).Diff(baseCands)
		if cands.Count() < 2 {
			continue
		}
		rect = []*Cell{c0, c1, c2, c3}
		zCells := []*Cell{c2, c3}
		var result *Result
		usedLine, resultLine := g.cdr3Line(rect, zCells)
		if usedLine {
			result = resultLine
		}
		usedBox, resultBox := g.cdr3Box(rect, zCells)
		if OUTPUTRESULT && usedBox && (!usedLine || !slices.Equal(resultLine.Removed, resultBox.Removed)) {
			result = resultBox
			if usedLine {
				result = mergeResults(resultLine, resultBox)
			}
		}

		used = usedLine || usedBox

		if used && OUTPUTRESULT {
			results = append(results, result)
		}

		return

	}

	return
}

func (c *Cell) getCandsOrValue() set.Set {
	if c.solved() {
		return set.NewSet(c.value - 1)
	}
	return c.cands
}

func (g *Grid) cdr3Line(rect, zCells []*Cell) (used bool, result *Result) {
	baseCands := rect[0].getCandsOrValue().Union(rect[1].getCandsOrValue())
	var line House
	if zCells[0].rowNum == zCells[1].rowNum {
		line = g.rows[zCells[0].rowNum]
	} else if zCells[0].colNum == zCells[1].colNum {
		line = g.cols[zCells[0].colNum]
	} else {
		return
	}
	cells := []*Cell{}
	for _, cell := range line.cells {
		if cell.solved() || (slices.Contains(zCells, cell)) {
			continue
		}
		cells = append(cells, cell)
	}
	remains := unionCellCands(zCells).Diff(baseCands)
	if ok, subset, subsetCands, rmCells := g.verifyNaked(remains, zCells, cells); ok {
		//fmt.Println("ok", rmCells)
		used = true
		if OUTPUTRESULT {
			/*result.addRemoved(subsetCands, rmCells)
			g1 := result.getHelpCandsGroup()
			g1.add(subsetCands, append(subset, zCells...))
			g2 := result.getHelpCandsGroup()
			g2.add(baseCands, rect)
			result.setStrategyName("Unique Rectangle Type3")*/
			result = new(Result)
			result.addRemoved(subsetCands, rmCells)
			g2 := result.getHelpCandsGroup()
			var g3 HelpValuesGroup
			for _, cell := range rect {
				if cell.solved() {
					g3 = result.getHelpValuesGroup()
					break
				}
			}
			for _, cell := range rect {
				if cell.solved() {
					g3.add([]*Cell{cell})
				} else {
					g2.add(baseCands, []*Cell{cell})
				}
			}
			g1 := result.getHelpCandsGroup()
			g1.add(subsetCands, append(subset, zCells...))
			result.setStrategyName("Counter Death Rectangle 3")
		}
		if CHANGE {
			removeCands(subsetCands, rmCells)
		}
	}
	return
}

func (g *Grid) cdr3Box(rect, zCells []*Cell) (used bool, result *Result) {
	baseCands := rect[0].getCandsOrValue().Union(rect[1].getCandsOrValue())
	if zCells[0].boxNum != zCells[1].boxNum {
		return
	}

	cells := []*Cell{}
	for _, cell := range g.boxes[zCells[0].boxNum].cells {
		if cell.solved() || (slices.Contains(zCells, cell)) {
			continue
		}
		cells = append(cells, cell)
	}

	remains := unionCellCands(zCells).Diff(baseCands)
	if ok, subset, subsetCands, rmCells := g.verifyNaked(remains, zCells, cells); ok {
		//fmt.Println("ok", rmCells)
		used = true
		if OUTPUTRESULT {
			result = new(Result)
			result.addRemoved(subsetCands, rmCells)
			g2 := result.getHelpCandsGroup()
			var g3 HelpValuesGroup
			for _, cell := range rect {
				if cell.solved() {
					g3 = result.getHelpValuesGroup()
					break
				}
			}
			for _, cell := range rect {
				if cell.solved() {
					g3.add([]*Cell{cell})
				} else {
					g2.add(baseCands, []*Cell{cell})
				}
			}
			g1 := result.getHelpCandsGroup()
			g1.add(subsetCands, append(subset, zCells...))
			result.setStrategyName("Counter Death Rectangle 3")
		}
		if CHANGE {
			removeCands(subsetCands, rmCells)
		}
	}
	return
}
